﻿using System;
using System.ComponentModel;
using Inspire.Main.Exceptions;
using System.Collections.Generic;
using Inspire.Classes.DataStructures;
using Inspire.Interfaces;

namespace Inspire.DataStructures
{
    public class SearchCriteria : INotifyPropertyChanged, IDataErrorInfo, IValidation
    {
        #region Properties

        // Defined lists of acceptable values
        public static readonly string[] searchOperators = new string[] { "And", "Or" };
        public static readonly string[] comparisonOperators = new string[] { "Is Equal To", "Contains", "Begins With", "Ends With" };

        /// <summary>
        /// SearchOperator: The boolean operator to use when evaluating this criteria with its neighbors.
        /// </summary>
        private string _searchOperator = "";
        public string SearchOperator
        {
            get
            {
                return this._searchOperator;
            }
            set
            {
                // Ensure the new value is acceptable
                string newValue = null;
                foreach (string acceptableOperator in SearchCriteria.searchOperators)
                {
                    if (acceptableOperator.Equals(value))
                    {
                        newValue = value;
                        break;
                    }
                }

                if (newValue == null)
                {
                    throw new InvalidValueAssignmentException("Attempted to assign invalid value [" + value + "] to property [SearchOperator].");
                }
                else
                {
                    OnSearchOperatorChanging(value);
                    this._searchOperator = value;
                    this.NotifyPropertyChanged("SearchOperator");
                }
            }
        }

        /// <summary>
        /// FiledName: Name of field to apply this Search Criteria to.
        /// </summary>
        private KeyValue<string, string> _fieldName = new KeyValue<string, string>("", "");
        public KeyValue<string, string> FieldName
        {
            get
            {
                return this._fieldName;
            }
            set
            {
                if (value.Key.Equals(""))
                {
                    throw new InvalidValueAssignmentException("Attempted to assign invalid value [\"\"] to property [FieldName].");
                }
                else
                {
                    OnFieldNameChanging(value.Value);
                    this._fieldName = value;
                    this.NotifyPropertyChanged("FieldName");
                }
            }
        }

        /// <summary>
        /// Not: If true, negate the Search Comparison Operator when applied to the Search Value.
        /// </summary>
        private bool _not = false;
        public bool Not
        {
            get
            {
                return this._not;
            }
            set
            {
                this._not = value;
                this.NotifyPropertyChanged("Not");
            }
        }

        /// <summary>
        /// ComparisonOperator: The comparison operator to use when searching the specified field for the Search Value.
        /// </summary>
        private string _comparisonOperator = "";
        public string ComparisonOperator
        {
            get
            {
                return this._comparisonOperator;
            }
            set
            {
                // Ensure the new value is acceptable
                string newValue = null;
                foreach (string acceptableOperator in SearchCriteria.comparisonOperators)
                {
                    if (acceptableOperator.Equals(value))
                    {
                        newValue = value;
                        break;
                    }
                }
                if (newValue == null)
                {
                    throw new InvalidValueAssignmentException("Attempted to assign invalid value [" + value + "] to property [ComparisonOperator].");
                }
                else
                {
                    OnComparisonOperatorChanging(value);
                    this._comparisonOperator = value;
                    this.NotifyPropertyChanged("ComparisonOperator");
                }
            }
        }

        /// <summary>
        /// SearchValue: The value to search the specified field for using the specified Comparison Operator.
        /// </summary>
        private string _searchValue = "";
        public string SearchValue
        {
            get
            {
                return this._searchValue;
            }
            set
            {
                // Ensure the new value is acceptable
                if (value == null)
                {
                    throw new InvalidValueAssignmentException("Attempted to assign invalid value [null] to property [SearchValue].");
                }
                else
                {
                    OnSearchValueChanging(value);
                    this._searchValue = value;
                    this.NotifyPropertyChanged("SearchValue");
                }
            }
        }

        /// <summary>
        /// EndGroup: Is this criteria the last condition in a group of conditions? Each group begins with an open parentheses. When the EndGroup property is true, the current group
        /// </summary>
        private bool _endGroup = false;
        public bool EndGroup
        {
            get
            {
                return this._endGroup;
            }
            set
            {
                this._endGroup = value;
                this.NotifyPropertyChanged("EndGroup");
            }
        }

        #endregion

        #region IDataErrorInfo Members

        /// <summary>
        /// Stores message messages mapped to the name of the property to which they belong.
        /// </summary>
        protected Dictionary<string, string> _errors = new Dictionary<string, string>();

        public string Error
        {
            get
            {
                return String.Empty;
            }
        }

        public string this[string columnName]
        {
            get
            {
                if (_errors.ContainsKey(columnName))
                {
                    return _errors[columnName];
                }
                return String.Empty;
            }
        }

        #endregion

        #region IValidation Members

        /// <summary>
        /// Validate the Search Operator
        /// </summary>
        /// <param name="value">Value to be validated</param>
        /// <returns>(string) Error message</returns>
        protected string ValidateSearchOperator(string value)
        {
            bool isValidated = false;
            
            // Ensure the value is a part of the list of operators
            foreach(string nextOperator in SearchCriteria.searchOperators)
            {
                if (nextOperator.Equals(value))
                {
                    isValidated = true;
                    break;
                }
            }

            if (!(isValidated))
            {
                return "The selected Search Operator is not a valid choice.";
            }

            return String.Empty;
        }

        /// <summary>
        /// Validate the Field Name
        /// </summary>
        /// <param name="value">Value to be validated</param>
        /// <returns>(string) Error message</returns>
        protected string ValidateFieldName(string value)
        {
            if (value == null || value.Trim().Length < 1)
            {
                return "A valid Field Name must be selected.";
            }

            return String.Empty;
        }

        /// <summary>
        /// Validate the Comparison Operator.
        /// </summary>
        /// <param name="value">Value to be validated</param>
        /// <returns>(string) Error message</returns>
        protected string ValidateComparisonOperator(string value)
        {
            bool isValidated = false;

            // Ensure the value is part of the list of operators
            foreach (string nextOperator in SearchCriteria.comparisonOperators)
            {
                if (nextOperator.Equals(value))
                {
                    isValidated = true;
                    break;
                }
            }

            if (!(isValidated))
            {
                return "The selected Comparison Operator is not a valid choice.";
            }

            return String.Empty;
        }

        /// <summary>
        /// Validate the Search Value
        /// </summary>
        /// <param name="value">Value to be validated</param>
        /// <returns>(string) Error message</returns>
        protected string ValidateSearchValue(string value)
        {
            if (value == null || value.Trim().Length < 1)
            {
                return "The Search Value must not be left blank.";
            }

            return String.Empty;
        }

        public bool Validate()
        {
            // Initialize isValidated to true
            bool isValidated = true;

            // Clear all errors
            this._errors.Clear();

            // Store the Error Message
            string message;

            #region Validate fields and add messages to _errors

            // SearchOperator
            message = ValidateSearchOperator(this.SearchOperator);
            if (message.Length > 0)
            {
                this._errors.Add("SearchOperator", message);
                isValidated = false;
            }

            // FieldName
            message = ValidateFieldName(this.FieldName.Value);
            if (message.Length > 0)
            {
                this._errors.Add("FieldName", message);
                isValidated = false;
            }

            // ComparisonOperator
            message = ValidateComparisonOperator(this.ComparisonOperator);
            if (message.Length > 0)
            {
                this._errors.Add("ComparisonOperator", message);
                isValidated = false;
            }

            // SearchValue
            message = ValidateSearchValue(this.SearchValue);
            if (message.Length > 0)
            {
                this._errors.Add("SearchValue", message);
                isValidated = false;
            }

            #endregion

            return isValidated;
        }

        public string[] GetAllErrors()
        {
            string[] allErrors = new string[this._errors.Count];

            int counter = 0;
            foreach (KeyValuePair<string, string> nextError in this._errors)
            {
                allErrors[counter] = nextError.Value;
                counter++;
            }

            return allErrors;
        }

        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion

        #region Property Event Handlers

        /// <summary>
        /// Event handler for changing of the Search Operator property.
        /// </summary>
        /// <param name="value">(string) The new value of the Search Operator property.</param>
        private void OnSearchOperatorChanging(string value)
        {
            // Get a Validation Message if it fails validation
            string errorMessage = this.ValidateSearchOperator(value);

            if (errorMessage.Length > 0)
            {
                this._errors.Add("SearchOperator", errorMessage);
            }
            else
            {
                this._errors.Remove("SearchOperator");
            }
        }

        /// <summary>
        /// Event handler for changing of the Field Name property.
        /// </summary>
        /// <param name="value">(string) The new value of the Field Name property.</param>
        private void OnFieldNameChanging(string value)
        {
            // Get a Validation Message if it fails validation
            string errorMessage = this.ValidateFieldName(value);

            if (errorMessage.Length > 0)
            {
                this._errors.Add("FieldName", errorMessage);
            }
            else
            {
                this._errors.Remove("FieldName");
            }
        }

        /// <summary>
        /// Event handler for changing of the Comparison Operator property.
        /// </summary>
        /// <param name="value">(string) The new value of the Comparison Operator property.</param>
        private void OnComparisonOperatorChanging(string value)
        {
            // Get a Validation Message if it fails validation
            string errorMessage = this.ValidateComparisonOperator(value);

            if (errorMessage.Length > 0)
            {
                this._errors.Add("ComparisonOperator", errorMessage);
            }
            else
            {
                this._errors.Remove("ComparisonOperator");
            }
        }

        /// <summary>
        /// Event handler for changing of the Search Value property.
        /// </summary>
        /// <param name="value">(string) The new value of the Search Value property.</param>
        private void OnSearchValueChanging(string value)
        {
            // Get a Validation Message if it fails validation
            string errorMessage = this.ValidateSearchValue(value);

            if (errorMessage.Length > 0)
            {
                this._errors.Add("SearchValue", errorMessage);
            }
            else
            {
                this._errors.Remove("SearchValue");
            }
        }

        #endregion
    }
}